<template>
	<div
		class="relative"
		ref="containerTarget"
		:style="{
			height: containerHeight + 'px',
		}"
	>
		<!-- 数据渲染 -->
		<template v-if="columnWidth && data.length">
			<div
				v-for="(item, index) in data"
				:key="nodeKey ? item[nodeKey] : index"
				class="m-waterful-item absolute duration-300"
				:style="{
					width: columnWidth + 'px',
					left: item._style?.left + 'px',
					top: item._style?.top + 'px',
				}"
			>
				<slot :item="item" :width="columnWidth" :index="index"></slot>
			</div>
		</template>

		<!-- 加载中 -->
		<div v-else>加载中...</div>
	</div>
</template>

<script setup>
import { computed } from '@vue/reactivity'
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import {
	getImgElements,
	getAllImg,
	onComplateImgs,
	getMinHeightColumn,
	getMinHeight,
	getMaxHeight,
} from './utils.js'

const props = defineProps({
	data: {
		type: Array,
		required: true,
	},
	nodeKey: {
		type: String,
	},
	column: {
		default: 2,
		type: Number,
	},

	columnSpacing: {
		default: 20,
		type: Number,
	},

	rowSpacing: {
		default: 20,
		type: Number,
	},

	picturePreReading: {
		type: Boolean,
		default: true,
	},
})

// 容器的总高度 = 最高的这一列的高度
const containerHeight = ref(0)

// 记录每列高度的容器
const columnHeightObj = ref({})

// 构建记录每列高度的对象
const useColumnHeightObj = () => {
	columnHeightObj.value = {}

	for (let i = 0; i < props.column; i++) {
		columnHeightObj.value[i] = 0
	}
}

// 容器实例
const containerTarget = ref(null)

// 容器总宽度(内容总宽度，不包含padding,margin,border)
const containerWidth = ref(0)

// 容器的左边距 计算item的left
const containerLeft = ref(0)

// 计算容器宽度 mdn:getComputedStyle
const useContainerWidth = () => {
	const { paddingLeft, paddingRight } = getComputedStyle(
		containerTarget.value,
		null
	)

	// 容器左边距
	containerLeft.value = parseFloat(paddingLeft)
	// 容器的宽度
	containerWidth.value =
		containerTarget.value.offsetWidth -
		parseFloat(paddingLeft) -
		parseFloat(paddingRight)
}

// 列宽
const columnWidth = ref(0)
// 列间距的合计
const columnSpacingTotal = computed(() => {
	return props.columnSpacing * (props.column - 1)
})

// 计算列宽
const useColumnWidth = () => {
	useContainerWidth()
	columnWidth.value =
		(containerWidth.value - columnSpacingTotal.value) / props.column
}

onMounted(() => {
	useColumnWidth()
})

// item高度集合
let itemHeights = []

// 监听图片加载完成(需要图片预加载)
const waitImgComplate = () => {
	itemHeights = []
	// 拿到所有的元素
	let itemElements = [
		...containerTarget.value.querySelectorAll('.m-waterful-item'),
	]
	// 获取到元素的img标签
	const imgElements = getImgElements(itemElements)
	// 获取所有img标签的图片
	const allImgs = getAllImg(imgElements)
	// 等待图片加载完成
	onComplateImgs(allImgs).then(() => {
		// 获取所有item的高度
		itemHeights = itemElements.map((item) => item.offsetHeight)

		// 渲染位置
		useItemLocation()
	})
}

// 不需要图片预加载
const useItemHeight = () => {
	itemHeights = []
	let itemElements = [
		...containerTarget.value.querySelectorAll('.m-waterful-item'),
	]
	itemHeights = itemElements.map((item) => item.offsetHeight)
	// 渲染位置
	useItemLocation()
}

// 返回下一个item的left
const getItemLeft = () => {
	const column = getMinHeightColumn(columnHeightObj.value)
	return (
		containerLeft.value + column * (columnWidth.value + props.columnSpacing)
	)
}

// 返回下一个item的top
const getItemTop = () => {
	return getMinHeight(columnHeightObj.value)
}

const increasingHeight = (index) => {
	const column = getMinHeightColumn(columnHeightObj.value)
	columnHeightObj.value[column] += itemHeights[index] + props.rowSpacing
}

const useItemLocation = () => {
	props.data.forEach((item, index) => {
		if (item._style) {
			return
		}
		item._style = {}
		item._style.left = getItemLeft()
		item._style.top = getItemTop()

		// 更新列高
		increasingHeight(index)
	})
	// 容器高度
	containerHeight.value = getMaxHeight(columnHeightObj.value)
}

// 在组件销毁的时候，清除所有的_style
onUnmounted(() => {
	props.data.forEach((item) => {
		delete item._style
	})
})

// 触发计算
watch(
	() => props.data,
	(newVal) => {
		nextTick(() => {
			// 第一次获取数据时，构建高度记录容器
			newVal.every((item) => !item._style) && useColumnHeightObj()

			if (props.picturePreReading) {
				waitImgComplate()
			} else {
				useItemHeight()
			}
		})
	},
	{
		deep: true,
		immediate: true,
	}
)

// 重新构建瀑布流
const reset = () => {
	setTimeout(() => {
		useColumnWidth()

		props.data.forEach((item) => {
			item._style = null
		})
	}, 200)
}

// 监听列数的变化
watch(
	() => props.column,
	() => {
		if (props.picturePreReading) {
			columnWidth = 0
			reset()
		} else {
			reset()
		}
	}
)
</script>

<style scoped lang="scss"></style>
